package simpledb;

import java.io.*;
import java.util.*;

/**
 * HeapFile is an implementation of a DbFile that stores a collection of tuples
 * in no particular order. Tuples are stored on pages, each of which is a fixed
 * size, and the file is simply a collection of those pages. HeapFile works
 * closely with HeapPage. The format of HeapPages is described in the HeapPage
 * constructor.
 *
 * @author Sam Madden
 * @see simpledb.HeapPage#HeapPage
 */
public class HeapFile implements DbFile {

    private File file;
    private TupleDesc tupleDesc;
    private int tableid;

    /**
     * Constructs a heap file backed by the specified file.
     *
     * @param f the file that stores the on-disk backing store for this heap
     *          file.
     */
    public HeapFile(File f, TupleDesc td) {
        // some code goes here
        file = f;
        tupleDesc = td;
        tableid = f.getAbsoluteFile().hashCode();
    }

    /**
     * Returns the File backing this HeapFile on disk.
     *
     * @return the File backing this HeapFile on disk.
     */
    public File getFile() {
        // some code goes here
        return file;
    }

    /**
     * Returns an ID uniquely identifying this HeapFile. Implementation note:
     * you will need to generate this tableid somewhere to ensure that each
     * HeapFile has a "unique id," and that you always return the same value for
     * a particular HeapFile. We suggest hashing the absolute file name of the
     * file underlying the heapfile, i.e. f.getAbsoluteFile().hashCode().
     *
     * @return an ID uniquely identifying this HeapFile.
     */
    @Override
    public int getId() {
        // some code goes here
        return tableid;
    }

    /**
     * Returns the TupleDesc of the table stored in this DbFile.
     *
     * @return TupleDesc of this DbFile.
     */
    @Override
    public TupleDesc getTupleDesc() {
        // some code goes here
        return tupleDesc;
    }

    // see DbFile.java for javadocs
    @Override
    public Page readPage(PageId pid) {
        // some code goes here
//        System.out.println("call HeapFile.readPage");
        BufferedInputStream bis = null;
        int offset = pid.getPageNumber() * BufferPool.getPageSize();
        try {
            bis = new BufferedInputStream(new FileInputStream(file));
            if (bis.skip(offset) !=
                    offset) {
                throw new NoSuchElementException();
            }
            byte[] pageBuf = new byte[BufferPool.getPageSize()];
            int retval = bis.read(pageBuf, 0, BufferPool.getPageSize());
            if (retval == -1) {
                throw new IllegalArgumentException("Read past end of table");
            }
            if (retval < BufferPool.getPageSize()) {
                throw new IllegalArgumentException("Unable to read "
                        + BufferPool.getPageSize() + " bytes from HeapFile");
            }

            Debug.log(1, "HeapFile.readPage: read page %d", pid.getPageNumber());
            HeapPage page = new HeapPage((HeapPageId) pid, pageBuf);
            return page;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // Close the file on success or error
            try {
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException ioe) {
                // Ignore failures closing the file
            }
        }
    }

    // see DbFile.java for javadocs
    @Override
    public void writePage(Page page) throws IOException {
        // some code goes here
        // not necessary for lab1
        HeapPageId pid = (HeapPageId) page.getId();
        int offset = pid.getPageNumber() * BufferPool.getPageSize();
        byte[] data = page.getPageData();
        RandomAccessFile rf = new RandomAccessFile(file, "rw");

        rf.seek(offset);
        rf.write(data);
        rf.close();

    }

    /**
     * Returns the number of pages in this HeapFile.
     */
    public int numPages() {
        // some code goes here
        return (int) (file.length() / BufferPool.getPageSize());
    }

    // see DbFile.java for javadocs
    @Override
    public ArrayList<Page> insertTuple(TransactionId tid, Tuple t)
            throws DbException, IOException, TransactionAbortedException {
        // some code goes here
        // not necessary for lab1
//        System.out.println("call HeapFile.insertTuple");
        ArrayList<Page> pages = new ArrayList<>();
        for (int i = 0; i < this.numPages(); i++) {
            HeapPage heapPage = (HeapPage) Database.getBufferPool().
                    getPage(tid, new HeapPageId(tableid, i),
                            Permissions.READ_WRITE);
            if (heapPage.getNumEmptySlots() == 0) {
                Database.getBufferPool().releasePage(tid,heapPage.pid);
                continue;
            }
            heapPage.insertTuple(t);
            pages.add(heapPage);
//            System.out.println("dbfile: "+tableid+" tid: "+tid+" pages: "+this.numPages()+" tuples: "+(heapPage.numSlots- heapPage.getNumEmptySlots()));
            return pages;
        }
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
                new FileOutputStream(file, true));
        byte[] emptyData = HeapPage.createEmptyPageData();
        bufferedOutputStream.write(emptyData);
        bufferedOutputStream.close();

        HeapPage heapPage = (HeapPage) Database.getBufferPool().
                getPage(tid, new HeapPageId(tableid, this.numPages() - 1),
                        Permissions.READ_WRITE);
        heapPage.insertTuple(t);
//        System.out.println("dbfile: "+tableid+" tid: "+tid+" pages: "+this.numPages()+" tuples: "+(heapPage.numSlots- heapPage.getNumEmptySlots()));
        pages.add(heapPage);
        return pages;
    }

    // see DbFile.java for javadocs
    @Override
    public ArrayList<Page> deleteTuple(TransactionId tid, Tuple t) throws DbException,
            TransactionAbortedException {
        // some code goes here
        // not necessary for lab1
//        System.out.println("call HeapFile.deleteTuple");
        ArrayList<Page> pages = new ArrayList<>();
        HeapPage heapPage = (HeapPage) Database.getBufferPool().
                getPage(tid, t.getRecordId().getPageId(), Permissions.READ_WRITE);
        heapPage.deleteTuple(t);
        pages.add(heapPage);
//        System.out.println("dbfile: "+tableid+" tid: "+tid+" pages: "+this.numPages()+" tuples: "+(heapPage.numSlots- heapPage.getNumEmptySlots()));
        return pages;
    }

    // see DbFile.java for javadocs
    @Override
    public DbFileIterator iterator(TransactionId tid) {
        // some code goes here
        return new HeapFileIterator(this, tid);
    }

    class HeapFileIterator extends AbstractDbFileIterator {
        private Iterator<Tuple> iterator = null;
        private HeapPage heapPage = null;
        private HeapPageId curHeapPageId = null;
        private TransactionId tid;
        private HeapFile f;
        private boolean isOpen;

        /**
         * Constructor for this iterator
         *
         * @param f   - the HeapFile containing the tuples
         * @param tid - the transaction id
         */
        public HeapFileIterator(HeapFile f, TransactionId tid) {
//            System.out.println("call class HeapFileIterator");
            this.tid = tid;
            this.f = f;
            isOpen = false;
        }

        /**
         * Open this iterator by getting an iterator on the first leaf page
         */
        @Override
        public void open() throws DbException, TransactionAbortedException {
//            System.out.println("call HeapFileIterator.open");
            isOpen = true;
            curHeapPageId = new HeapPageId(f.tableid, 0);
            heapPage = (HeapPage) Database.getBufferPool().getPage(
                    tid,
                    curHeapPageId,
                    Permissions.READ_ONLY);
            iterator = heapPage.iterator();
        }

        @Override
        public boolean hasNext() throws DbException, TransactionAbortedException {
            return super.hasNext();
        }

        @Override
        public Tuple next() throws DbException, TransactionAbortedException, NoSuchElementException {
//            System.out.println("HeapFileIterator.next");
            return super.next();
        }

        /**
         * Read the next tuple either from the current page if it has more tuples or
         * from the next page by following the right sibling pointer.
         *
         * @return the next tuple, or null if none exists
         */
        @Override
        protected Tuple readNext() throws DbException, TransactionAbortedException {
//            System.out.println("call HeapFileIterator.readNext");
            if (!isOpen) {
                return null;
            }
            if (iterator.hasNext()) {
                return iterator.next();
            }
            while (!iterator.hasNext()) {
                if (curHeapPageId.getPageNumber() >= f.numPages() - 1) {
                    return null;
                }
                HeapPageId nextPageId = new HeapPageId(
                        f.tableid,
                        curHeapPageId.getPageNumber() + 1);
                heapPage = (HeapPage) Database.getBufferPool().getPage(
                        tid, nextPageId, Permissions.READ_ONLY);
                curHeapPageId = nextPageId;
                iterator = heapPage.iterator();
            }
            return iterator.next();
        }

        /**
         * rewind this iterator back to the beginning of the tuples
         */
        @Override
        public void rewind() throws DbException, TransactionAbortedException {
            close();
            open();
        }

        @Override
        public void close() {
            super.close();
            isOpen = false;
            iterator = null;
            heapPage = null;
        }


    }

}

